/*
 * Copyright 2011-2012 by The Regents of the University of California Licensed
 * under the Apache License, Version 2.0 (the "License"); you may not use this
 * file except in compliance with the License. you may obtain a copy of the
 * License from
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package edu.ucsd.db.bassarisk.scoring;

import edu.ucsd.db.bassarisk.exception.UnexpectedCaseException;
import edu.ucsd.db.bassarisk.exception.VisitorException;
import edu.ucsd.db.bassarisk.query.binding.BindingFactory;
import edu.ucsd.db.bassarisk.query.binding.Bindings.AggregatedValueBinding;
import edu.ucsd.db.bassarisk.query.binding.Bindings.Binding;
import edu.ucsd.db.bassarisk.query.binding.Bindings.PositionBinding;
import edu.ucsd.db.bassarisk.query.binding.Bindings.RunBinding;
import edu.ucsd.db.bassarisk.query.bindingtree.BindingTreeElements.BindingTreeEdge;
import edu.ucsd.db.bassarisk.query.bindingtree.BindingTreeElements.EstablishingNode;
import edu.ucsd.db.bassarisk.query.path.DSDElementVisitor;
import edu.ucsd.db.bassarisk.query.path.DSDElements.DSDAggregate;
import edu.ucsd.db.bassarisk.query.path.DSDElements.DSDContext;
import edu.ucsd.db.bassarisk.query.path.DSDElements.DSDDescent;
import edu.ucsd.db.bassarisk.query.path.DSDElements.DSDFilter;
import edu.ucsd.db.bassarisk.schema.AttributeClass;
import edu.ucsd.db.bassarisk.schema.AttributeClass.AttributeSort;
import edu.ucsd.db.bassarisk.schema.DataSequenceClass;
import edu.ucsd.db.bassarisk.schema.RunClass;
import edu.ucsd.db.bassarisk.treepattern.EdgeFunction;

public class SimpleScoringSignalBranchBuilder implements
    DSDElementVisitor<Binding, EstablishingNode> {
  private final BindingFactory bindingFactory;

  public SimpleScoringSignalBranchBuilder(final BindingFactory bindingFactory) {
    this.bindingFactory = bindingFactory;
  }

  private EstablishingNode makeDescentBinding(final DSDDescent elem,
                                              final EstablishingNode parent) {
    DataSequenceClass seqClass = elem.getSequenceClass();
    Binding newBinding = null;
    if (seqClass instanceof RunClass) {
      if (seqClass.isPositionSpace()) {
        newBinding = this.bindingFactory.createPositionBinding();
      } else {
        newBinding = this.bindingFactory.createStructureBinding((RunClass) seqClass);
      }
    } else if (seqClass instanceof AttributeClass) {
      AttributeClass attSeqClass = (AttributeClass) seqClass;
      if (attSeqClass.attributeSort() == AttributeSort.Position) {
        PositionBinding attributeMask =
            (PositionBinding) parent.getEstablishedBinding();
        newBinding =
            this.bindingFactory.createPositionAttributeBinding(attSeqClass,
                                                               attributeMask);
      } else {
        RunBinding attributeMask = (RunBinding) parent.getEstablishedBinding();
        newBinding =
            this.bindingFactory
                .createRunAttributeBinding(attSeqClass, attributeMask);
      }
    } else {
      throw UnexpectedCaseException.ofSubtype(DataSequenceClass.class);
    }
    return new EstablishingNode(newBinding);
  }

  @Override
  public Binding visitAggregate(final DSDAggregate elem,
                                final EstablishingNode parent)
      throws VisitorException {
    if (elem.getFunction() == EdgeFunction.NonEmpty) {
      return this.visitDescent(elem, parent);
    }
    EstablishingNode newTip = this.makeDescentBinding(elem, parent);
    AggregatedValueBinding newAggregateBinding =
        this.bindingFactory.createAggregatedValueBinding(parent
            .getEstablishedBinding(), elem.getFunction());
    BindingTreeEdge tipEdge = new BindingTreeEdge(newTip, newAggregateBinding);
    tipEdge.functions().add(elem.getFunction());
    parent.addChild(tipEdge);
    if (elem.hasNext()) {
      newAggregateBinding.setInputSequence(elem.getNext().accept(this, newTip));
    } else {
      newAggregateBinding.setInputSequence(newTip.getEstablishedBinding());
    }
    return newAggregateBinding;
  }

  @Override
  public Binding visitContext(final DSDContext elem, final EstablishingNode parent)
      throws VisitorException {
    if (elem.hasNext()) {
      return elem.getNext().accept(this, parent);
    } else {
      return null;
    }
  }

  @Override
  public Binding visitDescent(final DSDDescent elem, final EstablishingNode parent)
      throws VisitorException {
    EstablishingNode newTip = this.makeDescentBinding(elem, parent);
    BindingTreeEdge newLead = new BindingTreeEdge(newTip);
    newLead.functions().add(elem.isFilter() ? EdgeFunction.NonEmpty
                                : EdgeFunction.Values);
    parent.addChild(newLead);
    if (elem.hasNext()) {
      return elem.getNext().accept(this, newTip);
    } else {
      // this is the target of any aggregation stage
      return newTip.getEstablishedBinding();
    }
  }

  @Override
  public Binding visitFilter(final DSDFilter elem, final EstablishingNode parent)
      throws VisitorException {
    throw VisitorException.invalidNodeType();
  }
}
